package com.isp.common.web.aspect;


import com.isp.common.model.vo.Notification;
import com.isp.common.model.vo.Result;
import com.isp.common.model.vo.request.IBaseRequest;
import com.isp.common.utils.JacksonUtils;
import com.isp.common.web.annotation.ApiLog;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class ApiLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApiLogAspect.class);

    private final static ThreadLocal<Long> START_TIME = new ThreadLocal<>();
    private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

    @Pointcut("@annotation(com.isp.common.web.annotation.ApiLog)")
    public void methodAspect() {

    }

    @Before(value = "methodAspect()")
    public void before(JoinPoint joinPoint) {
        START_TIME.set(System.currentTimeMillis());
    }

    @AfterReturning(value = "methodAspect()", returning = "methodResult")
    public void afterReturn(JoinPoint joinPoint, Object methodResult) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            String requestURI = null;
            String requestMethod = null;
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                requestURI = request.getRequestURI();
                requestMethod = request.getMethod();
            }

            Long startTime = START_TIME.get();
            Long endTime = System.currentTimeMillis();
            long totalTime = endTime - startTime;
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            Method method = ms.getMethod();
            String clazz = method.getDeclaringClass().getName();
            Map<Object, Object> requestParam = getParam(joinPoint, method);
            // 封装响应参数
            String responseStr = methodResult.toString();
            if (methodResult instanceof Result) {
                Result result = (Result) methodResult;
                responseStr = JacksonUtils.toJsonString(result);
            }
            Notification notification = new Notification.NotificationBuilder(DATE_FORMAT.format(startTime), DATE_FORMAT.format(endTime))
                    .apiUrl(requestURI).time(totalTime + "ms").clazz(clazz).method(requestMethod)
                    .request(JacksonUtils.toJsonString(requestParam)).response(responseStr).build();
            LOGGER.info(notification.toString());
        } finally {
            START_TIME.remove();
        }
    }

    @AfterThrowing(value = "methodAspect()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            String requestURI = null;
            String requestMethod = null;
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                requestURI = request.getRequestURI();
                requestMethod = request.getMethod();
            }

            Long startTime = START_TIME.get();
            Long endTime = System.currentTimeMillis();
            long totalTime = endTime - startTime;
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            Method method = ms.getMethod();
            String clazz = method.getDeclaringClass().getName();
            ApiLog apiLog = method.getAnnotation(ApiLog.class);
            Map<Object, Object> requestParam = getParam(joinPoint, method);
            String message = ExceptionUtils.getMessage(e);
            String hostAddress = null;
            try {
                InetAddress address = InetAddress.getLocalHost();
                hostAddress = address.getHostAddress();
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
            }
            Notification notification = new Notification.NotificationBuilder(DATE_FORMAT.format(startTime), DATE_FORMAT.format(endTime))
                    .apiUrl(requestURI).method(requestMethod).time(totalTime + "ms").host(hostAddress).clazz(clazz)
                    .request(JacksonUtils.toJsonString(requestParam)).exceptionMessage(message).build();
            if (apiLog.isWarning()) {
                LOGGER.error(notification.toString(), e);
            }
        } finally {
            START_TIME.remove();
        }
    }

    private Map<Object, Object> getParam(JoinPoint joinPoint, Method method) {
        Map<Object, Object>  jsonObject = new HashMap<>();
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            Map<Object, Object>  requestParam = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof IBaseRequest) {
                    jsonObject = JacksonUtils.parseMap(JacksonUtils.toJsonString(args[i]), Object.class, Object.class);
                    break;
                } else {
                    requestParam.put(paramNames[i], args[i]);
                }
            }
            jsonObject.put("request_param", requestParam);
        }
        return jsonObject;
    }
}
